import fs from 'fs';
import process from 'process';
import path, { dirname } from 'path';
import { exec, execSync } from 'child_process'
import jszip from 'jszip'
import { BuildConfig, BuildInfo } from './classes.mjs';

//----
Date.prototype.format = function (fmt) {
    var o = {
        "M+": this.getMonth() + 1,                 //月份 
        "d+": this.getDate(),                    //日 
        "h+": this.getHours(),                   //小时 
        "m+": this.getMinutes(),                 //分 
        "s+": this.getSeconds(),                 //秒 
        "q+": Math.floor((this.getMonth() + 3) / 3), //季度 
        "S": this.getMilliseconds()             //毫秒 
    };
    if (/(y+)/.test(fmt)) {
        fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
    }
    for (var k in o) {
        if (new RegExp("(" + k + ")").test(fmt)) {
            fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
        }
    }
    return fmt;
}

//遍历文件夹
export const DirForeach = async (dirPath, isRec = false, cbDir, cbFile, cbExitDir = null) => {
    const fsDir = fs.opendirSync(dirPath);
    let childDirent = fsDir.readSync();
    while (childDirent) {
        if (childDirent.isDirectory()) {
            cbDir && await cbDir(childDirent, path.resolve(dirPath, childDirent.name));
            if (isRec) {
                await DirForeach(path.resolve(dirPath, childDirent.name), isRec, cbDir, cbFile, cbExitDir);
            }
        }
        else {
            cbFile && await cbFile(childDirent, path.resolve(dirPath, childDirent.name));
        }
        childDirent = fsDir.readSync();
    }
    fsDir.closeSync();
    cbExitDir && cbExitDir();
};

export const FixPath = (path) => {
    return path.replaceAll("./", "").replaceAll("/", "\\");
}
//复制所有文件
/**
 * 
 * @param {string} fromPath 
 * @param {string} toPath 
 * @param {string} exclude 暂不能正常使用 
 */

export const CopyDir = async (fromPath, toPath, exclude = "") => {
    let copyStatment = `echo D| xcopy /y /c /s /h /r ` + FixPath(`"${fromPath}\\*.*" "${toPath}" `);
    if (exclude) {
        copyStatment += `/EXCLUDE:"${FixPath(exclude)}"`;
    }
    console.log("复制文件夹", copyStatment);
    execSync(copyStatment);
};

/**
 * 
 * @param {string} fromPath 
 * @param {string} toPath 
 */
export const CopyFile = async (fromPath, toPath) => {
    let copyStatment = `echo F| xcopy /y /c /s /h /r ` + `"${fromPath}" "${toPath}" `.replaceAll("./", "").replaceAll("/", "\\");
    console.log("复制文件", copyStatment);
    execSync(copyStatment);
};

export const MkDir = async (dirPath) => {
    if (!fs.existsSync(dirPath)) {
        fs.mkdirSync(dirPath);
    }
};
/**
 * 
 * @param {string} dirPath 
 */
export const Rename = async (srcPath, dstPath) => {
    fs.renameSync(srcPath, dstPath);
};

/**
 * 
 * @param {string} dirPath 
 */
export const RmDir = async (dirPath) => {
    if (fs.existsSync(dirPath)) {
        let delStatment = `del /f /s /q ` + `"${dirPath}\\*.*" `.replaceAll("./", "").replaceAll("/", "\\");
        console.log("删除文件夹", dirPath);
        execSync(delStatment);
        execSync(`rd /s /q ` + `"${dirPath}"`.replaceAll("./", "").replaceAll("/", "\\"));
    }
};
/**
 * 
 * @param {string} dirPath 
 */
export const RmDirToJob = async (dirPath) => {
    if (fs.existsSync(dirPath)) {
        let delStatment = `del /f /s /q ` + `"${dirPath}\\*.*" `.replaceAll("./", "").replaceAll("/", "\\");
        Asyncer.addJob(new Promise(async ok => {
            exec(delStatment).on("close", ok);
        }));
        Asyncer.addJob(new Promise(async ok => {
            exec(`rd /s /q ` + `"${dirPath}"`.replaceAll("./", "").replaceAll("/", "\\")).on("close", ok);
        }));
    }
};

export const GetMD5Path = async (inPath, fileName, extName) => {
    if (!fs.existsSync(inPath)) {
        console.warn("GetMD5Path路径无效");
        return "";
    }
    let outPath = "";
    await DirForeach(inPath, false,
        (dirent, path) => { //dir
        },
        (dirent, path) => { //file
            let ext = dirent.name.substring(dirent.name.lastIndexOf(".") + 1);
            if (dirent.name.match(fileName) && ext == extName) {
                outPath = path;
            }
        }
    );
    return outPath;
}

export const HasArg = (arg) => {
    let argvs = process.argv.slice(2);
    return !!argvs.find(ele => ele == arg);
}
export const GetArgInd = (arg) => {
    let argvs = process.argv.slice(2);
    return argvs.findIndex(ele => ele == arg);
}
export const GetArgValueByInd = (ind) => {
    return process.argv.slice(2)[ind];
}
export const GetBuildTarget = () => {
    let argvs = process.argv.slice(2);
    if (!!argvs.find(ele => ele == "res")) {
        return "res";
    }
    else if (!!argvs.find(ele => ele == "test")) {
        return "test";
    }
    return "";
};

export const ReadBuildConfig = (target) => {
    const ROOT_PATH = FindProjectRoot();
    let url = path.resolve(ROOT_PATH, `build_config_${target}.json`);
    if (!fs.existsSync(url)) {
        return null;
    }

    let config = new BuildConfig();
    Object.assign(config, JSON.parse(fs.readFileSync(url).toString()));
    return config;
}

//寻找项目地址
export const FindProjectRoot = (extPath) => {
    if (extPath) {
        if (extPath[0] == "/") {
            extPath = extPath.substring(1);
        }
        if (extPath[extPath.length] == "/") {
            extPath = extPath.substring(0, extPath.length - 1);
        }
    }

    let cwd = process.cwd();
    let reTry = 4;
    while (reTry > 0) {
        if (fs.existsSync(`${cwd}/assets`) && fs.existsSync(`${cwd}/project.json`)) {
            break;
        }
        // console.log("当前不是项目文件夹，向左寻找项目", cwd);
        cwd = path.resolve(cwd, "../");
        reTry--;
    }
    if (!fs.existsSync(`${cwd}/assets`) || !fs.existsSync(`${cwd}/project.json`)) {
        return "";
    }

    if (extPath) {
        return cwd + "/" + extPath;
    }
    else {
        return cwd;
    }
}


/**
 * 压缩
 * @param {string} zipFilePath 
 * @param {string} dirPath 
 */
export const ZipDir = async (zipFilePath, dirPath) => {
    if (dirPath[dirPath.length - 1] == "/" || dirPath[dirPath.length - 1] == "\\") {
        dirPath = dirPath.substring(0, dirPath.length - 1);
    }
    let dirName = "";
    if (!dirName) {
        let ind = dirPath.lastIndexOf("/");
        if (ind == -1) {
            ind = dirPath.lastIndexOf("\\");
        }
        if (ind != -1) {
            dirName = dirPath.substring(ind + 1);
        }
        else {
            dirName = dirPath;
        }
    }

    const zip = new jszip();
    let zf = zip.folder(dirName);
    let stack = [zf];
    await DirForeach(dirPath, true,
        async (dirent, path) => { //dir
            stack.unshift(stack[0].folder(dirent.name));
        },
        async (dirent, path) => { //file
            stack[0].file(dirent.name, fs.readFileSync(path));
        },
        (dirent, path) => { //close dir
            stack.shift();
        },
    );

    let content = await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
    fs.writeFileSync(zipFilePath, content);
};

//后台PM
export class Asyncer {
    static _allDoneCb = null;
    static jobs = [];
    static addJob(job) {
        this.jobs.push(job);
        job.then(() => {
            let ind = this.jobs.findIndex(ele => ele == job)
            if (ind !== -1) {
                this.jobs.splice(ind, 1);
            }
            if (this._allDoneCb && this.jobs.length === 0) {
                this._allDoneCb();
            }
        });
    }
    static async awaitAllJobDone() {
        console.log("--- 等待所有任务完成");
        if (this.jobs.length !== 0) {
            await new Promise(ok => {
                this._allDoneCb = ok;
            });
        }
        console.log("--- 所有任务完成");
    }
}

export const GetBundleList = async () => {
    const rootPath = FindProjectRoot();
    let bundleList = [];
    await DirForeach(path.resolve(rootPath, `build/web-mobile/assets`), false,
        async (dirent, url) => { //dir
            let bundleName = path.basename(url);
            if (bundleName != "internal" && bundleName != "main") {
                bundleList.push(path.basename(url));
            }
        },
        async (dirent, path) => { //file
        }
    );
    return bundleList;
}

//builders
export const BuildBundle = async (bundleRootPath, bundleList, info, doZip) => {
    const rootPath = FindProjectRoot();
    const buildPath = path.resolve(rootPath, "build/web-mobile");
    console.table(bundleList);
    for (let i = 0; i < bundleList.length; i++) {
        const bundleName = bundleList[i];
        const bundlePath = path.resolve(bundleRootPath, bundleName);
        if (fs.existsSync(bundlePath)) {
            await RmDir(bundlePath);
        }
        // await MkDir(bundlePath);
        await CopyDir(path.resolve(buildPath, `assets/${bundleName}`), bundlePath);

        //修改文件
        const indexMD5Path = await GetMD5Path(bundlePath, "index", "js");
        const indexPath = path.resolve(bundlePath, "index.js");
        let fileStr = fs.readFileSync(indexMD5Path).toString();
        fileStr.replace(new RegExp(`BUNDLE_${bundleName.toUpperCase()}_BUILD_TARGET="(.+?)"`, "g"), `BUNDLE_${bundleName.toUpperCase()}_BUILD_TARGET="${info.target}"`);
        fs.writeFileSync(indexPath, fileStr);

        if (doZip) {
            const zipFilePath = path.resolve(bundleRootPath, `${bundleName}-${buildTarget}-${(new Date()).format("yy-MM-dd_hh_mm_ss")}.zip`);
            await ZipDir(zipFilePath, bundlePath);
            console.log("--- ZIP完成");
        }
    }
}


export const BuildFrame = async (doZip) => {
    const __dirname = dirname(process.argv[1]);
    const rootPath = FindProjectRoot();
    if (!rootPath) {
        console.log(`项目文件夹错误`);
        process.exit(1);
    }
    console.log(`项目文件夹${rootPath}`);


    const buildPath = path.resolve(rootPath, "build/web-mobile");
    const info = new BuildInfo();
    if (fs.existsSync(path.resolve(buildPath, "build.info"))) {
        Object.assign(info, JSON.parse(fs.readFileSync(path.resolve(buildPath, `build.info`)).toString()));
        if (!info.mod) {
            console.warn("已打包，还没修改文件，请先执行build.mjs -m");
            process.exit(0);
        }
        if (!info.compress) {
            console.warn("注意：已打包，未进行压缩。");
        }
    }
    else {
        console.warn("还没打包，请先执行build.mjs");
        process.exit(0);
    }
    const buildTarget = info.target;
    const framePath = path.resolve(rootPath, `build/frame-${buildTarget}`);

    //读取配置
    /**
     * @type { BuildConfig }
     */
    const config = new BuildConfig();
    switch (buildTarget) {
        case "test":
            Object.assign(config, JSON.parse(fs.readFileSync(path.resolve(rootPath, `build_config_test.json`)).toString()));
            break;
        case "res":
            Object.assign(config, JSON.parse(fs.readFileSync(path.resolve(rootPath, `build_config_res.json`)).toString()));
            break;
    }

    const tmpPath = path.resolve(rootPath, `build/tmp`);
    if (fs.existsSync(tmpPath)) {
        await RmDir(`${tmpPath}`);
    }
    await MkDir(`${tmpPath}`);
    //删除旧BUILD
    if (fs.existsSync(framePath)) {
        await RmDir(framePath);
    }
    //复制文件
    await CopyDir(buildPath, framePath);
    await Rename(`${framePath}/assets`, `${tmpPath}/assets`);
    await MkDir(`${framePath}/assets`);
    await Rename(`${tmpPath}/assets/main`, `${framePath}/assets/main`);
    await Rename(`${tmpPath}/assets/internal`, `${framePath}/assets/internal`);
    await RmDirToJob(`${tmpPath}/assets`);

    //修改文件
    const getGameConfigTplJsPath = path.resolve(__dirname, "get_game_config.tpl.js")
    let getGameConfigTplJsStr = fs.readFileSync(getGameConfigTplJsPath).toString();
    getGameConfigTplJsStr = getGameConfigTplJsStr;

    const indexPath = await GetMD5Path(framePath, "index", "html");
    let str = fs.readFileSync(indexPath).toString();
    str = str
        .replace(`};LoadCocos();`, "}\n" + getGameConfigTplJsStr)
        ;
    fs.writeFileSync(indexPath, str);

    if (doZip) {
        //压缩zip
        let zipFilePath = path.resolve(rootPath, `build/frame-${buildTarget}-${(new Date()).format("yy-MM-dd_hh_mm_ss")}.zip`);
        await ZipDir(zipFilePath, framePath);
    }
    console.log("--- 框架构建完成。");
}

export const BuildGame = async (dstPath, uid, info, doZip) => {
    const rootPath = FindProjectRoot();
    const buildPath = path.resolve(rootPath, "build/web-mobile");
    if (!rootPath) {
        console.log(`项目文件夹错误`);
        process.exit(1);
    }
    console.log(`项目文件夹${rootPath}`);
    if (!info.mod) {
        console.warn("已打包，还没修改文件，请先执行build.mjs -m");
        process.exit(0);
    }
    if (!info.compress) {
        console.warn("注意：已打包，未进行压缩。");
    }
    const buildTarget = info.target;
    //读取配置
    /**
     * @type { BuildConfig }
     */
    const config = new BuildConfig();
    switch (buildTarget) {
        case "test":
            Object.assign(config, JSON.parse(fs.readFileSync(path.resolve(rootPath, `build_config_test.json`)).toString()));
            break;
        case "res":
            Object.assign(config, JSON.parse(fs.readFileSync(path.resolve(rootPath, `build_config_res.json`)).toString()));
            break;
    }

    //寻找游戏game_config_${target}.json
    let gameConfigs = [];
    let gameConfigNames = [];
    let gameConfigPath = path.resolve(rootPath, `assets/bundle/configs/${buildTarget}`);
    await DirForeach(gameConfigPath, false,
        async (dirent, path) => { //dir
        },
        async (dirent, path) => { //file
            let extName = path.substring(path.lastIndexOf(".") + 1, path.length);
            if (extName && extName == "json") {
                let conf = JSON.parse(fs.readFileSync(path).toString());
                gameConfigs.push(conf);
                gameConfigNames.push(dirent.name);
            }
        },
        (dirent, path) => { //close dir
        },
    );
    console.table(gameConfigNames);
    const gameRootPath = path.resolve(rootPath, dstPath);
    if (!fs.existsSync(gameRootPath)) {
        await MkDir(gameRootPath);
    }

    for (let i = 0; i < gameConfigs.length; i++) {
        const conf = gameConfigs[i];
        const gameKey = conf.key + uid;
        const gamePath = path.resolve(gameRootPath, gameKey);
        if (fs.existsSync(gamePath)) {
            await RmDir(gamePath);
        }
        await MkDir(gamePath);

        for (let bundleName in conf.bundleConfigCol) {
            let bundleConfig = conf.bundleConfigCol[bundleName];
            //如果包不是引用包，则复制文件。
            if (!bundleConfig.url) {
                //复制文件。
                await CopyDir(path.resolve(buildPath, "assets", bundleName), path.resolve(gamePath, bundleName));
                //配置远程地址。
                bundleConfig.url = config.gamePath + "/" + gameKey;
            }
            else {
                bundleConfig.url = config.bundlePath;
            }
        }

        //复制静态资源
        if (conf.h5Assets) {
            conf.h5Assets.forEach(async url => {
                if (url[url.length - 1] == "/" || url[url.length - 1] == "\\") {
                    //文件夹
                    console.log("h5Assets dir:", url);
                    await CopyDir(path.resolve(rootPath, url), path.resolve(gamePath, url));
                }
                else {
                    console.log("h5Assets file:", url);
                    await CopyFile(path.resolve(rootPath, url), path.resolve(gamePath, url));
                }
            });
        }

        //写游戏配置到游戏包中。
        fs.writeFileSync(path.resolve(gamePath, "config.json"), JSON.stringify(conf));

        if (doZip) {
            //打包
            const zipFilePath = path.resolve(gameRootPath, `${conf.key}-${buildTarget}-${uid ? `${uid}-` : ""}${(new Date()).format("yy-MM-dd_hh_mm_ss")}.zip`);
            await ZipDir(zipFilePath, gamePath);
            console.log("--- ZIP完成");
        }
    }
}
